package com.xqboss.task.socket;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xqboss.common.core.domain.R;
import com.xqboss.common.utils.LocalDateUtils;
import com.xqboss.common.utils.StringUtils;
import com.xqboss.common.utils.spring.SpringUtils;
import com.xqboss.framework.web.service.TokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author Administrator
 */
@ServerEndpoint("/apps/websocket/{token}")
@Component
public class WebSocketServer {

    //与某个客户端的连接会话，需要通过它来给客户端发送数据
    private Session session;

    //用户id
    private String userId;

    //concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
    //虽然@Component默认是单例模式的，但springboot还是会为每个websocket连接初始化一个bean，所以可以用一个静态set保存起来。
    //  注：底下WebSocket是当前类名
    private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();
    // 用来存：在线连接数
    private static final ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    private static final TokenService tokenService = SpringUtils.getBean(TokenService.class);


    private String getUserId(String token) {
        try {
            return tokenService.getUseridFromToken(token);
        } catch (Exception e) {
            log.error("websocket>>>解析失败>>getUserId" + token, e);
            return null;
        }
    }

    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "token") String token) {
        try {
            final String userId = getUserId(token);
            if (userId!=null) {
                this.session = session;
                this.userId = userId;
                //用户上线
                tokenService.setOnlineStatus(this.userId, true);
                webSockets.add(this);
                sessionPool.put(userId, session);

//                log.info("任务模块【websocket消息】有新的连接，总数为：{}, 线程map中：{}", webSockets.size(), JSONUtil.toJsonStr(sessionPool.keySet()));
                sendMessage(session, ">连接成功<");
            } else {
                log.info("websocket>>>连接创建>>解析失败>>" + token);
                sendMessage(session, "解析错误, 暂无权限连接");
                session.close();
            }

        } catch (Exception e) {
            log.error("WebSocketServer.onOpen()链接失败原因：", e);
            e.printStackTrace();
        }
    }

    /**
     * 发送消息，每次浏览器刷新，session会发生变化。（同步发送 getBasicRemote）
     *
     * @param session
     * @param message
     */
    public static void sendMessage(Session session, String message) {
        try {
//            session.getBasicRemote().sendText(String.format("%s (From Server，Session ID=%s)", message, session.getId()));
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("发送消息出错：{}", e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 异步发送（推荐使用 getAsyncRemote）
     *
     * @param session
     * @param message
     */
    public static void sendAsyncMessage(Session session, String message) {
        session.getAsyncRemote().sendText(message);
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
            //用户下线
            tokenService.setOnlineStatus(this.userId, false);
            //删除socket连接
            webSockets.remove(this);

            log.info("【websocket消息】连接断开，总数为:" + webSockets.size());
        } catch (Exception e) {
            log.error("WebSocketServer.onClose()链接关闭错误原因：", e);
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
//        log.info("【websocket消息】收到客户端消息:" + message);
        if (StringUtils.isEmpty(message)) {
            return;
        }
        String req;
        if (getJSONType(message)) {
            // {"req":"online","userId":"xxx"}
            // {"req":"onlines","userIds":["xxx","xxx"]}
            req = JSONUtil.parseObj(message).getStr("req");
        } else {
            req = message;
        }
        switch (req){
            case "Ping":
                Map<String, Object> map = new HashMap<>();
                map.put("userId", "");
                map.put("toUserId", "");
                map.put("sessionId", this.session.getId());
                map.put("timestamp", LocalDateUtils.getCurrentLocalDateTime());
                map.put("type", null);
                map.put("sourceId", null);
                map.put("msg", "");
                map.put("avatar", "");
                R<Map<String, Object>> ping = R.ok(map, "Pong");
                String str = JSONUtil.toJsonStr(ping);
                sendAsyncMessage(this.session, str);
                break;
            case "online":
                String id = JSONUtil.parseObj(message).getStr("userId");
                if (StringUtils.isNotEmpty(id)) {
                    Session session = sessionPool.get(id);
                    boolean isOnline = session != null && session.isOpen();
                    Map<Object, Object> build = MapUtil.builder().put("req", "online").put(id,isOnline).build();
                    R<Map<Object, Object>> onlineInfo = R.ok(build, "公会大厅列表页，单个用户在线状态");
                    String onlineStr = JSONUtil.toJsonStr(onlineInfo);
                    log.info("公会大厅列表页，单个用户在线状态:{}" ,onlineStr);
                    sendAsyncMessage(this.session, onlineStr);
                }
                break;
            case "onlines":
                JSONArray userIds = JSONUtil.parseObj(message).getJSONArray("userIds");
                if (ObjUtil.isNotEmpty(userIds) && userIds.size() > 0) {
                    HashMap<String, Boolean> m = new HashMap<>(userIds.size());
                    for (Object userId : userIds) {
                        Session session = sessionPool.get((String) userId);
                        boolean isOnline = session != null && session.isOpen();
                        m.put((String) userId, isOnline);
                    }
                    Map<Object, Object> build = MapUtil.builder().put("req", "onlines").put("onlineInfos", m).build();
                    R<Map<Object, Object>> onlineInfos = R.ok(build, "公会大厅列表页，当前分页用户在线状态");
                    String onlineStr = JSONUtil.toJsonStr(onlineInfos);
                    log.info("公会大厅列表页，当前分页用户在线状态:{}" ,onlineStr);
                    sendAsyncMessage(this.session, onlineStr);
                }
                break;
            default:
                break;
        }
    }

    /**
     * 判断是否是json字符串
     *
     * 简单判断是否为json格式 ，判断规则：判断首尾字母是否为{}或[]，如果都不是则不是一个JSON格式的文本。
     *
     * @param str
     * @return
     */
    public static boolean getJSONType(String str) {
        boolean result = false;
        if (StringUtils.isNotBlank(str)) {
            str = str.trim();
            if (str.startsWith("{") && str.endsWith("}")) {
                result = true;
            } else if (str.startsWith("[") && str.endsWith("]")) {
                result = true;
            }
        }
        return result;
    }

    /**
     * 发送错误时的处理
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.info("用户错误,原因:" + error.getMessage());
//        error.printStackTrace();
    }

    // 此为广播消息
    public static void sendAllMessage(String message) {
        log.info("【websocket消息】广播消息:" + message);
        for (WebSocketServer webSocket : webSockets) {
            try {
                if (webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                log.error("广播消息出错：", e);
            }
        }
    }

    // 此为单点消息
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            try {
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                log.error("单点消息出错：" + e);
            }
        } else {
            log.info("没找到目前已经建立连接的用户，即要推送给的用户目前没登录");
        }
    }

    // 此为单点消息(多人)
    public void sendMoreMessage(String[] userIds, String message) {
        for (String userId : userIds) {
            Session session = sessionPool.get(userId);
            if (session != null && session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:" + message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    log.error("单点消息(多人)出错：" + e);
                }
            }
        }
    }
}